/**
 * PHOSIDE: PHosphorylation Site IDentification Engine.
 * Copyright 2009 Digital Biology Lab, University of Missouri.
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 3 of the License, or (at your option) any later
 * version. <p/> This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License for more
 * details.
 */

package phoside.classify.result;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import phoside.Protein;
import phoside.ProteinImpl;
import phoside.Proteins;
import phoside.PhosphoProteinsImpl;

import phoside.util.ListUtil;
import phoside.util.StaticFinalValues;

/**
 *
 * @author gjj
 */
public class PhosidePredictionResultImpl extends PhosphoProteinsImpl
        implements PhosidePredictionResult, Serializable {
    private static final long serialVersionUID = -8898352608262800965L;

    private List<Protein> serializeProteins;

    private ResultInterpreter resInt;

    private transient double threshold = Double.POSITIVE_INFINITY;

    private transient List<Double> allScores = null;

    public PhosidePredictionResultImpl() {
        this(null);
    }

    public PhosidePredictionResultImpl(ResultInterpreter resInt) {
        this.resInt = resInt;
    }

    public PhosidePredictionResultImpl(ResultInterpreter resInt, Proteins proteins) {
        super(proteins, true, new String[]{StaticFinalValues.ACCESSION,
            StaticFinalValues.SEQUENCE, StaticFinalValues.SYMBOL,
            StaticFinalValues.DESCRIPTION, StaticFinalValues.SPECIES});
        //super(proteins, true, new String[]{StaticFinalValues.ACCESSION});
        this.resInt = resInt;
    }

//    public PhosidePredictionResultImpl(ResultInterpreter resInt, Set<String> accessions) {
//        this.resInt = resInt;
//        for (String acc : accessions) {
//            mapAccessionProtein.put(acc, new ProteinImpl());
//        }
//    }

    public void setResultInterpreter(ResultInterpreter resInt) {
        this.resInt = resInt;
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        serializeProteins = new ArrayList(proteinCount());

        Set<String> info = new HashSet();
        info.add(StaticFinalValues.ACCESSION);
        info.add(StaticFinalValues.PREDICTION);

        Iterator<Protein> it = proteinIterator();
        while (it.hasNext()) {
            Protein protein = new ProteinImpl(it.next());
            //protein.removeInfo(StaticFinalValues.SITES); // remove all sites to save space
            protein.keepInfo(info);
            serializeProteins.add(protein);
        }
        out.defaultWriteObject();
        serializeProteins = null;
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        for (Protein protein : serializeProteins) {
            addProtein(protein);
        }
        serializeProteins = null;
        threshold = Double.POSITIVE_INFINITY;
    }

    public ResultInterpreter getResultInterpreter() {
        return resInt;
    }

    public List<Double> getPredictions(String accession) {
        if (accession==null) {
            throw new NullPointerException("accession cannot be null");
        }

        Protein protein = getProtein(accession);
        if (protein == null)
            return null;

        Object obj = protein.getInfo(StaticFinalValues.PREDICTION);
        if (obj==null)
            return null;

        return (List<Double>)protein.getInfo(StaticFinalValues.PREDICTION);
    }

    public double getThrehold() {
        return threshold;
    }

    public void setPrediction(String accession, int site, double prediction) {
        if (accession==null) {
            throw new NullPointerException();
        }

        Protein proInfo = mapAccessionProtein.get(accession);
        if (proInfo==null) {
            throw new IllegalStateException("Add protein first: "+accession);
        }

        String proteinSeq = proInfo.getSequence();
        if (proteinSeq==null) {
            throw new IllegalStateException("No sequence information available.");
        }

        if (proteinSeq.length()<=site || site<0) {
            System.err.println(accession+"\t"+site);
            throw new java.lang.IndexOutOfBoundsException();
        }

        List<Double> predictions = getPredictions(accession);
        if (predictions==null) {
            predictions = new ArrayList(proteinSeq.length());
            ListUtil.fillList(predictions, Double.NEGATIVE_INFINITY, proteinSeq.length());
            proInfo.putInfo(StaticFinalValues.PREDICTION, predictions);
        }

        predictions.set(site, prediction);

        allScores = null;
    }

    public void setPredictions(String accession, List<Double> predictions) {
        if (accession==null || predictions==null) {
            throw new NullPointerException();
        }

        Protein proInfo = mapAccessionProtein.get(accession);
        if (proInfo==null) {
            throw new IllegalStateException("Add protein first.");
        }

        String proteinSeq = proInfo.getSequence();
        if (proteinSeq==null) {
            throw new IllegalStateException("No sequence information available.");
        }

        if (proteinSeq.length()!=predictions.size()) {
            throw new IllegalStateException("protein sequence and prediction are not in the same length.");
        }

        proInfo.putInfo(StaticFinalValues.PREDICTION, predictions);

        allScores = null;
    }

    public void setThreshold(double threshold) {
        if (this.threshold == threshold)
            return;

        this.threshold = threshold;

        Iterator<Protein> it = this.proteinIterator();
        while (it.hasNext()) {
            Protein protein = it.next();
            String acc = protein.getAccession();
            List<Double> pred = getPredictions(acc);
            if (pred==null)
                continue;

            Set<Integer> sites = new TreeSet();
            int n = pred.size();
            for (int i=0; i<n; i++) {
                if (pred.get(i)>=threshold) {
                    sites.add(i);
                }
            }
            protein.putInfo(StaticFinalValues.SITES, sites);
        }
        
    }

    public PhosidePredictionResult copyTo(PhosidePredictionResult result,
            boolean phosphoOnly, boolean alphabetical) {
        if (result==null)
            result = new PhosidePredictionResultImpl(resInt);

        if (!phosphoOnly && !alphabetical) {
            result.addAll(this, false, null, ConfictHandleOption.RENAME);
            return result;
        }

        List<Protein> pros = new ArrayList(proteins());
        if (alphabetical) {
            Collections.sort(pros, new java.util.Comparator<Protein>() {
                public int compare(Protein pro1, Protein pro2) {
                    return pro1.getAccession().compareTo(pro2.getAccession().toString());
                }
            });
        }

        if (!phosphoOnly) {
            for (Protein pro : pros) {
                result.addProtein(pro);
            }
        } else {
            for (Protein pro : pros) {
                Set<Integer> sites = getSites(pro.getAccession());
                if (sites!=null && !sites.isEmpty()) {
                    result.addProtein(pro);
                }
            }
        }

        if (result instanceof PhosidePredictionResultImpl) {
            ((PhosidePredictionResultImpl)result).allScores = null;
        }

        return result;
    }

    public List<Double> descendPredictionScores() {
        if (allScores==null) {
            allScores = new ArrayList();

            for (String acc : getProteinsAccessions()) {
                List<Double> scores = getPredictions(acc);
                if (scores==null) continue;
                for (double score : scores) {
                    if (!Double.isInfinite(score)) {
                        allScores.add(score);
                    }
                }
            }

            java.util.Collections.sort(allScores,new java.util.Comparator() {
                public int compare(Object o1, Object o2) {
                    return -((Double)o1).compareTo((Double)o2);
                }
            });
        }

        return allScores;
    }
}
